﻿#region license
# This file is part of Grawlix.
# 
# Grawlix is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# 
# Grawlix is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with Grawlix.  If not, see
# <http://www.gnu.org/licenses/>.
#endregion

"""
Duplicate a field, method, property, type, etc.

Idea comes from Avishay Lavie describing an Alias attribute.
"""

namespace Grawlix.Meta
import System
import Boo.Lang.Compiler

transient class DuplicateAttribute(AbstractAstAttribute):
"""Duplicate the node it is applied to."""
	
	def constructor([required] name as Ast.ReferenceExpression):
	"""
	Create the alias attribute with specified name.
	Param name: The name of the alias to create.
	"""
		_name = name
	
	override def Apply(targetNode as Ast.Node):
	"""
	Apply the attribute to the specified node.
	Param targetNode: The node to apply to.
	"""
		# Node checking
		member = targetNode as Ast.TypeMember
		if member is null:
			InvalidNodeForAttribute('Member')
			return
		
		# ApplyToSelf argument
		applySelf = false
		if ApplyToSelf is not null:
			applySelf = ApplyToSelf.Value
		if applySelf and targetNode.NodeType != Ast.NodeType.Method:
			raise InvalidOperationException(
				"When ApplyToSelf is set to true, " +
				"DuplicateAttribute can only be applied " +
				"to a static method with at least one argument.")
		
		# The cloned typemember
		clone as Ast.TypeMember
		
		# Duplication cases: attempt to avoid actual code duplication
		if member.NodeType == Ast.NodeType.Method:
			method = member as Ast.Method
			if applySelf and \
				not method.IsStatic or \
				method.Parameters.Count == 0:
				raise InvalidOperationException(
					"When ApplyToSelf is set to true, " +
					"DuplicateAttribute can only be applied " +
					"to a static method with at least one argument.")
			clone = CloneMethod(method, applySelf)
		elif member.NodeType == Ast.NodeType.Property:
			clone = CloneProperty(member as Ast.Property)
		else:
			clone = member.CloneNode()
		
		# Do common duplication
		clone.LexicalInfo = LexicalInfo
		clone.Name = Name.Name
		# May need to do this recursively if duplicating types
		TypeSystemServices.Bind(clone, null)
		member.DeclaringType.Members.Add(clone)
	
	protected def CloneMethod(method as Ast.Method, applySelf as bool):
	"""
	Clone a given method.
	Param method: The method to clone.
	Param applySelf: Whether to substitute self for the first parameter.
	Returns: A clone of the method.
	"""
		clone = method.CloneNode()
		clone.Body.Clear()
		invoke = createDeferredInvocation(method, method.Name)
		if applySelf:
			clone.Parameters.RemoveAt(0)
			clone.Modifiers &= ~Ast.TypeMemberModifiers.Static
			invoke.Arguments.ReplaceAt(0, [| self |])
		if returnsValue(method):
			body = [| return $invoke |]
			body.LexicalInfo = LexicalInfo
			clone.Body.Add(body)
		else:
			clone.Body.Add(invoke)
		return clone
	
	protected static def CloneProperty(property as Ast.Property):
	"""
	Clone a given method.
	Param property: The property to clone.
	Returns: A clone of the property.
	"""
		clone = property.CloneNode()
		reference = Ast.ReferenceExpression(property.Name)
		if property.Getter is not null:
			clone.Getter.Body.Clear()
			clone.Getter.Body.Statements.Add([| return $reference |])
		if property.Setter is not null:
			clone.Setter.Body.Clear()
			statement = Ast.ExpressionStatement([| $reference = value |])
			clone.Setter.Body.Statements.Add(statement)
		return clone
	
	[getter(Name)]
	private _name as Ast.ReferenceExpression
	"""The name of the alias."""

	[property(ApplyToSelf)]
	private _applyToSelf as Ast.BoolLiteralExpression
	"""
	Applied to a static method, make.
	Remarks: Causes an error when not a static method.
	"""
	
	